/*
** Effect Skeleton for GSP260
**
** Revision: $Id: Render Demo.cpp 17 2010-09-11 01:25:07Z Tim Brengle $
*/
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <assert.h>
#include "resource.h"
#include "Renderer.h"
#include "StopWatch.h"


const unsigned int cnScreenWidth = 480;
const unsigned int cnScreenHeight = 480;

/* List of all objects */
ObjectList vAllObjects;

/* flag to stop drawing when invisible */
bool bDrawing = true;

/* Prototypes */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR psCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HWND OpenWindow(const char* cszClassName, const char* cszWindowName, int nCmdShow);
void CreateObjects(ObjectList& vAllObjects);
void DestroyObjects(ObjectList& objects);
void Update(ObjectList& objects, ObjectList& toBeDrawn);


/*****************************************************\
* WinMain                                             *
\*****************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	/* Create the main window */
	HWND hWnd = OpenWindow("D3DWindow", "D3D Skeleton", nCmdShow);
	if (NULL == hWnd)
	{
		return E_FAIL;
	}

	/* Create renderer and set up D3D in that window */
	Renderer::Instance()->InitializeD3D(hWnd);

	/* List of objects to be drawn this frame. Modified by Update() */
	ObjectList toDraw;

	/*
	** Create all the objects and anything else needed
	*/
	CreateObjects(vAllObjects);

	/* Message loop */
	MSG uMsg;
    memset(&uMsg, 0, sizeof(uMsg));
	while (uMsg.message != WM_QUIT)
	{
		/* Handle incoming messages */
		while (PeekMessage(&uMsg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&uMsg);
			DispatchMessage(&uMsg);
		}

		/*
		** All received messages have been handled, so do one frame of our game
		*/

		/* Update everything and note which need to be drawn */
		Update(vAllObjects, toDraw);

		/* Draw the results */
		if (bDrawing)
		{
			Renderer::Instance()->RenderOneFrame(toDraw);
		}
		else
		{
			::OutputDebugString("Paused\n");
			::Sleep(1000);
		}
	}

	DestroyObjects(vAllObjects);

    UnregisterClass("D3DWindow", hInstance);

	return uMsg.wParam;
}


/*****************************************************\
* OpenWindow                                          *
\*****************************************************/
HWND OpenWindow(const char* cszWinClassName, const char* cszWindowName, int nCmdShow)
{
	/* Register window class and create window */

	WNDCLASSEX winClass;
	HWND hWnd;

	HINSTANCE hInstance = ::GetModuleHandle(NULL);
	assert(NULL != hInstance);
	HICON hIcon = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
	assert(NULL != hIcon);

	winClass.lpszClassName = cszWinClassName;
	winClass.cbSize        = sizeof(WNDCLASSEX);
	winClass.style         = CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc   = WindowProc;
	winClass.hInstance     = hInstance;
	winClass.hIcon	       = hIcon;
    winClass.hIconSm	   = hIcon;
	winClass.hCursor       = ::LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground = (HBRUSH)::GetStockObject(BLACK_BRUSH);
	winClass.lpszMenuName  = NULL;
	winClass.cbClsExtra    = 0;
	winClass.cbWndExtra    = 0;

	if (0 == RegisterClassEx(&winClass))
	{
		return NULL;
	}

	hWnd = ::CreateWindowEx(NULL,
							cszWinClassName,
							cszWindowName,
							WS_OVERLAPPEDWINDOW,
							0, 0,
							cnScreenWidth, cnScreenHeight,
							NULL, NULL, ::GetModuleHandle(NULL), NULL);

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return hWnd;
}


/*****************************************************\
* WindowProc example                                  *
\*****************************************************/
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static bool bFirst = true;
	static D3DXVECTOR3 vOriginalView;
	static D3DXVECTOR3 vMouseView;
	static POINT vOldCursor;
	D3DXVECTOR3 vView;

    switch (uMsg)
	{
		/*
		** WM_ACTIVATE is about recovering from minimizing or being covered, etc.
		**		Notice and handle when we are partially or completely covered or minimized
		**		Stop drawing until we are visible again!
		*/
		case WM_ACTIVATE:
			{
				WORD wAction = LOWORD(wParam);
				WORD isMinimized = HIWORD(wParam);
				switch (wAction)
				{
					case WA_ACTIVE:
					case WA_CLICKACTIVE:
						/* Activated */
						bDrawing = true;
						return 0;

					case WA_INACTIVE:
						/* Deactivated */
						bDrawing = false;
						return 0;
				}
			}
			return 0;
		/* end WM_ACTIVATE */

		case WM_MOUSEWHEEL:
			{
				if (bFirst)
				{
					vOriginalView = Renderer::Instance()->GetViewPoint();
					bFirst = false;
				}
				/* Move closer and farther away by multiplicative factor */
				const float cfMoveMultiplier = 1.5f;	/* movement factor */

				int iKeys = GET_KEYSTATE_WPARAM(wParam);
				int iDelta = GET_WHEEL_DELTA_WPARAM(wParam);
				D3DXVECTOR3 vView = Renderer::Instance()->GetViewPoint();		
				if (iDelta > 0)
				{
					vView /= cfMoveMultiplier;
				}
				else
				{
					vView *= cfMoveMultiplier;
				}
				Renderer::Instance()->SetViewPoint(vView);
			}
			return 0;

		case WM_LBUTTONDOWN:
			/* Save mouse location and current viewpoint */
			vOldCursor.x = LOWORD(lParam);
			vOldCursor.y = HIWORD(lParam);

			if (bFirst)
			{
				vOriginalView = Renderer::Instance()->GetViewPoint();
				bFirst = false;
			}
			vMouseView = vOriginalView;
			return 0;

		case WM_MOUSEMOVE:
			{
				/*
				** Move the view point
				*/

				/* Only interested if left button pressed */
				if (0 != (wParam & MK_LBUTTON))
				{
					/* The left button is down. Move cursor */
					POINT vNewCursor;
					vNewCursor.x = LOWORD(lParam);
					vNewCursor.y = HIWORD(lParam);
					float fDeltaX = (float)(vNewCursor.x - vOldCursor.x) * .0005f;
					float fDeltaY = (float)(vNewCursor.y - vOldCursor.y) * .0005f;

					/* Adjust the viewPoint */
					D3DXMATRIX matRotate;
					D3DXMatrixRotationYawPitchRoll(&matRotate, fDeltaX, -fDeltaY, 0);
					D3DXVec3TransformCoord(&vMouseView, &vMouseView, &matRotate);
					/* Set the view point */
					Renderer::Instance()->SetViewPoint(vMouseView);
				}
			}
			return 0;

		case WM_KEYUP:
			switch (wParam)
			{
				case 'A':
					Renderer::Instance()->ToggleLightKind(AMBIENT);
					return 0;

				case 'D':
					Renderer::Instance()->ToggleLightKind(DIFFUSE);
					return 0;

				case 'S':
					Renderer::Instance()->ToggleLightKind(SPECULAR);
					return 0;

				case 'R':
					Renderer::Instance()->ToggleSpinning();
					return 0;

				case 'W':
					{
						FILL_MODE f = Renderer::Instance()->GetFillMode();
						Renderer::Instance()->SetFillMode((SOLID == f) ? WIREFRAME : SOLID);
					}
					return 0;

				case 'F':
					Renderer::Instance()->SetShadingMode(FLAT);
					return 0;

				case 'G':
					Renderer::Instance()->SetShadingMode(GOURAUD);
					return 0;

				case 'M':
					Renderer::Instance()->ToggleMaterialDemo();
					return 0;

				case 'O':
					Renderer::Instance()->SetViewPoint(vOriginalView);
					return 0;

				case 'T':
					Renderer::Instance()->ToggleTextures();
					return 0;

				case 'N':
					CreateObjects(vAllObjects);
					return 0;

				case VK_ESCAPE:
					PostQuitMessage(0);
					break;

				default:
					return 1;
			}

		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}


/*****************************************************\
* CreateObjects example                                *
\*****************************************************/
void CreateObjects(ObjectList& vAllObjects)
{
	/* TODO: Replace this with code about our game objects */

	for each(Object* pObject in vAllObjects)
	{
		assert(NULL != pObject);
		delete pObject;
	}
	vAllObjects.clear();

	const char* szModelFile;
	static unsigned nModelIndex = 0;
	static const char* vModelFiles[] =
	{
		"snowman.x",
		"snowman.x"
	};

	D3DXVECTOR3 leftLoc(60.0f, 0.0f, 0.0f);
	D3DXVECTOR3 rightLoc(-60.0f, 0.0f, 0.0f);

	struct INI_STRUCT 
	{
		const char* meshFile;	// model used to represent the model
		const char* texture;	// texture used to represent the model
		D3DXVECTOR3 vPos;		// the object's position in the world
		float fAngularSpeed;	// speed and direction at which the object spins
		float fRevolvingSpeed;	// speed and direction at which the object revolves around the sun
		float fScale;

	};

	//array to hold each object's unique properties so they can be assigned in the right order
	INI_STRUCT vInit[] = 
	{
		//first object
		{
			"models/template planet.x", 
			"textures/sun texture.png", 
			D3DXVECTOR3(0.0f, 0.0f, 0.0f),
			1.0f,
			0.0f,
			5.0f
			
		},

		//second object
		{
			"models/template planet.x",
			"textures/mercury texture.jpg", 
			D3DXVECTOR3(-100.0f, 0.0f, 0.0f),
			-5.0f,
			0.5f,
			1.0f
		}

		//third object, etc
	};


	szModelFile = vModelFiles[nModelIndex];
	/* Start pointing at the next model name, wrapping around */
	nModelIndex = (nModelIndex + 1) % NUM_ELEMENTS(vModelFiles);


	//loop runs through twice, creating two separate objects
	for(unsigned n = 0; n < 2; ++n)
	{
		Object* pObject = new Object;									// creates a new object
		pObject->m_vPosition = vInit[n].vPos;							// assigns new object's position
		pObject->m_fAngularVelocity = vInit[n].fAngularSpeed;			// assigns new object's angular speed
		pObject->m_fRevolvingVelocity = vInit[n].fRevolvingSpeed;		// assigns new object's revolving speed
		
		Renderer::Instance()->LoadMeshFile(vInit[n].meshFile, pObject->m_pMesh); //loads new object's mesh
		D3DXMatrixScaling(&pObject->m_mScale, vInit[n].fScale, vInit[n].fScale, vInit[n].fScale);

		//loads new object's texture if there isn't one
		if(NULL == pObject->m_pTexture)
		{
			Renderer::Instance()->LoadTextureFile(vInit[n].texture, pObject->m_pTexture);
			pObject->m_dwSubset = 0;
		}


		vAllObjects.push_back(pObject);	//push finished object into vector container
	}
}

/*****************************************************\
* DestroyObjects example                              *
\*****************************************************/
void DestroyObjects(ObjectList& objects)
{
	for each (Object* pObj in objects)
	{
		assert(pObj != NULL);
		pObj->m_pTexture->Release();
		pObj->m_pMesh->Release();
	}
}


/*****************************************************\
* Update example                                      *
\*****************************************************/
void Update(ObjectList& vAllObjects, ObjectList& vToBeDrawn)
{
	/* Figure out deltaTime since previous call to Update() */
	double deltaTime = StopWatch::Instance()->GetDeltaT();

	/* Clear the list of things to be drawn. Filled in loop */
	vToBeDrawn.clear();

	/*
	** Update the AI, FSM, etc:
	**  Move everything forward one frame in time
	**  Keep track of the ones needing to be drawn
	*/
	for each (Object* pObj in vAllObjects)
	{
		if (pObj->Update(deltaTime))
		{
			vToBeDrawn.push_back(pObj);
		}
	}
}
